/*******************************************************************************
 * Copyright (c) 2003, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors: IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.test.internal.performance;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.StringTokenizer;

class PerformanceMonitorLinux extends PerformanceMonitor {

    private static long    PAGESIZE         = 4096;
    private static long    JIFFIES          = 10L;
    private static boolean fgHasElapsedTime = true;
    private static long    fgStartupTime;

    /**
     * Write out operating system counters for Linux.
     */
    @Override
    protected void collectOperatingSystemCounters(Map scalars) {
        synchronized (this) {
            /**
             * The status values for a Linux process, that is the values that come from /proc/self/stat. The names of the variables match
             * the man proc page.
             */
            StringTokenizer st = readTokens("/proc/self/stat", false); //$NON-NLS-1$
            if (st != null) {
                st.nextToken(); // int pid; // Process id.
                st.nextToken(); // String comm; // The command name.
                st.nextToken(); // String state;
                st.nextToken(); // int ppid; // Parent process id. */
                st.nextToken(); // int pgrp; // Process group. */
                st.nextToken(); // int session;
                st.nextToken(); // int ttry_nr;
                st.nextToken(); // int tpgid;
                st.nextToken(); // long flags;
                long minflt = Long.parseLong(st.nextToken()); // Minor page faults (didn't need to load a page from disk). */
                st.nextToken(); // long cminflt; // Minor page faults for the process and it's children. */
                long majflt = Long.parseLong(st.nextToken()); // Major page faults. */
                st.nextToken(); // long cmajflt; // Major page faults for the process and it's children. */
                long utime = Long.parseLong(st.nextToken()); // User time in jiffies. */
                long stime = Long.parseLong(st.nextToken()); // System time in jiffies. */
                st.nextToken(); // long cutime; // User time for the process and it's children. */
                st.nextToken(); // long cstime; // System time for the process and it's children. */

                // addScalar(scalars, InternalDimensions.USER_TIME, utime*JIFFIES);
                addScalar(scalars, InternalDimensions.KERNEL_TIME, stime * JIFFIES);
                addScalar(scalars, InternalDimensions.CPU_TIME, (utime + stime) * JIFFIES);
                addScalar(scalars, InternalDimensions.SOFT_PAGE_FAULTS, minflt);
                addScalar(scalars, InternalDimensions.HARD_PAGE_FAULTS, majflt);
            }

            /**
             * The status memory values values for a Linux process, that is the values that come from /proc/self/statm. The names of the
             * variables match the man proc page.
             */
            st = readTokens("/proc/self/statm", false); //$NON-NLS-1$
            if (st != null) {
                st.nextToken(); // int size; // Size of the process in pages
                int resident = Integer.parseInt(st.nextToken()); // Resident size in pages.
                st.nextToken(); // int shared; // Shared size in pages.
                int trs = Integer.parseInt(st.nextToken()); // Text (code) size in pages.
                int drs = Integer.parseInt(st.nextToken()); // Data/Stack size in pages.
                int lrs = Integer.parseInt(st.nextToken()); // Library size in pages.
                // st.nextToken(); // int dt; // Dirty pages.

                addScalar(scalars, InternalDimensions.WORKING_SET, resident * PAGESIZE);
                addScalar(scalars, InternalDimensions.TRS, trs * PAGESIZE);
                addScalar(scalars, InternalDimensions.DRS, drs * PAGESIZE);
                addScalar(scalars, InternalDimensions.LRS, lrs * PAGESIZE);
            }

            long currentTime = System.currentTimeMillis();

            addScalar(scalars, InternalDimensions.SYSTEM_TIME, currentTime);

            if (fgHasElapsedTime) {
                if (fgStartupTime == 0) {
                    String t = System.getProperty("eclipse.startTime"); //$NON-NLS-1$
                    if (t != null) {
                        try {
                            fgStartupTime = Long.parseLong(t);
                        } catch (NumberFormatException e) {
                            fgHasElapsedTime = false;
                        }
                    } else
                        fgHasElapsedTime = false;
                }
                if (fgHasElapsedTime)
                    addScalar(scalars, InternalDimensions.ELAPSED_PROCESS, currentTime - fgStartupTime);
            }

            super.collectOperatingSystemCounters(scalars);
        }
    }

    /**
     * Write out the global machine counters for Linux.
     */
    @Override
    protected void collectGlobalPerformanceInfo(Map scalars) {
        synchronized (this) {
            /**
             * The meminfo values for a Linux machine, that is the values that come from /proc/meminfo.
             */
            // /proc/meminfo is formatted on linux - use byte-counted output from free
            StringTokenizer st = readOutput(true, "free", "-b"); //$NON-NLS-1$
            if (st != null) {
                st.nextToken(); // throw away label
                long total = Long.parseLong(st.nextToken());
                long used = Long.parseLong(st.nextToken());
                long free = Long.parseLong(st.nextToken());
                st.nextToken(); // long shared;
                long buffers = Long.parseLong(st.nextToken());
                long cache = Long.parseLong(st.nextToken());

                addScalar(scalars, InternalDimensions.PHYSICAL_TOTAL, total);
                addScalar(scalars, InternalDimensions.USED_LINUX_MEM, used);
                addScalar(scalars, InternalDimensions.FREE_LINUX_MEM, free);
                addScalar(scalars, InternalDimensions.BUFFERS_LINUX, buffers);
                addScalar(scalars, InternalDimensions.SYSTEM_CACHE, cache);
            }
            super.collectGlobalPerformanceInfo(scalars);
        }
    }

    private StringTokenizer readTokens(String procPath, boolean skipFirst) {
        try (BufferedReader rdr = new BufferedReader(new FileReader(procPath));){
            if (skipFirst)
                rdr.readLine(); // throw away the heading line
            return new StringTokenizer(rdr.readLine());
        } catch (IOException e) {
            PerformanceTestPlugin.log(e);
        }
        return null;
    }

    private StringTokenizer readOutput(boolean skipFirst, String... cmd) {
        BufferedReader rdr = null;
        try {
            Process process = Runtime.getRuntime().exec(cmd);
            rdr = new BufferedReader(new InputStreamReader(process.getInputStream()));
            if (skipFirst)
                rdr.readLine(); // throw away the heading line
            return new StringTokenizer(rdr.readLine());
        } catch (IOException e) {
            PerformanceTestPlugin.log(e);
        } finally {
            try {
                if (rdr != null)
                    rdr.close();
            } catch (IOException e) {
                // silently ignored
            }
        }
        return null;
    }
}
